package cn.tedu.charging.order.service.impl;

import cn.tedu.charging.order.common.GunStatusConstants;
import cn.tedu.charging.order.common.MqttConstants;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.mqtt.MqttProducer;
import cn.tedu.charging.order.pojo.dto.ChargingDto;
import cn.tedu.charging.order.pojo.param.OrderAddParam;
import cn.tedu.charging.order.service.OrderService;
import cn.tedu.common.JsonResult;
import cn.tedu.common.pojo.param.GunStatusUpdateParam;
import cn.tedu.common.pojo.vo.StationInfoVO;
import cn.tedu.common.pojo.vo.UserInfoVO;
import cn.tedu.common.utils.JsonUtils;
import io.github.classgraph.json.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.Random;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    MqttProducer mqttProducer;

    /**
     * 1. 生成 订单编号 生成的逻辑
     *
     * 2. 修改 充电枪 的 状态 为 使用中 (微服务架构  订单服务调用 设备服务 服务之间通信)
     * 3. 获取 用户信息 （车辆id）  (微服务架构  订单服务 调用 用户服务 服务之间通信)
     * 4. 获取 服务信息/设备信息 (场站信息 和 设备信息 充电站id, 运营商id ... ）    (微服务架构  订单服务 调用 设备服务 服务之间通信)
     *
     * 5. 订单服务 给 设备 发送开始充电指令  (物联网 订单服务 调用 硬件充电桩设备 服务与 设备之间通信）
     * @param orderAddParam
     * @return
     */
    @Override
    public String createOrder(OrderAddParam orderAddParam) {
        log.debug("创建订单-orderService:入参:{}",orderAddParam);

        //1 生成订单号
        log.debug("创建订单-orderService:生成订单号");
        String orderNo = getOrderNo();
        log.debug("创建订单-orderService:生成订单号:{}",orderNo);

        //2. 修改 充电枪 的 状态 为 使用中
        Integer gunId = orderAddParam.getGunId();
        Integer using = GunStatusConstants.GUN_STATUS_USING;
        log.debug("创建订单-orderService:修改枪状态入参:{},{}",gunId,using);
        Boolean gunStatusUpdate = updateGunStatus(gunId,using);
        log.debug("创建订单-orderService:修改枪状态入参:{},{},出参:{}",gunId,using,gunStatusUpdate);

        //3 获取 用户信息 （车辆id）
        Integer userId = orderAddParam.getUserId();
        log.debug("创建订单-orderService:获取用户信息入参:{}",userId);
        UserInfoVO userInfoVO = getUserInfoByOpenFeign(orderAddParam.getUserId());
        log.debug("创建订单-orderService:获取用户信息入参:{},出参:{}",userId,userInfoVO);

        //4. 获取 服务信息/设备信息
        log.debug("创建订单-orderService:获取服务信息/设备信息入参:{}",gunId);
        StationInfoVO stationInfoVO = getStationInfo(gunId);
        log.debug("创建订单-orderService:获取服务信息/设备信息入参:{},出参:{}",gunId,stationInfoVO);

        //5. 订单服务 给 设备 发送开始充电指令
        log.debug("创建订单-orderService:发送开始充电指令入参:订单号{},桩id:{},枪id:{}",
                orderNo,orderAddParam.getPileId(),orderAddParam.getGunId());
        Boolean startResult = startCharging(orderNo, orderAddParam.getPileId(), orderAddParam.getGunId());
        log.debug("创建订单-orderService:发送开始充电指令入参:订单号{},桩id:{},枪id:{},出参:{}",
                orderNo,orderAddParam.getPileId(),orderAddParam.getGunId(),startResult);

        log.debug("创建订单-orderService:入参:{},出参:{}",orderAddParam,orderNo);
        return orderNo;
    }

//    @Autowired
//    private MqttProducer mqttProducer;
//
//    /**
//     * 发送开始充电指令给EMQX 通知设备
//     * @return
//     */
//    private Boolean startCharging(String orderNo,
//                                  Integer pileId,
//                                  Integer gunId){
//        //1> Topic的设计
//        // 枪维度   一个枪一个Topic topic_桩id_枪id
//        // 桩维度   一个桩一个Topic topic_桩id
//        // 用桩维度 的topic的数量会比 枪维度的topic数量理论上会少 节省资源
//        // 用桩维度 消息体应该包含枪信息  枪id
//        // /topic/start/桩id  /topic/start/1
//        String topic = MqttConstants.TOPIC_START_CHARGING + pileId;
//
//        // 2> 消息体的设计 包含什么样的数据 通过业务目标分析
//        // 业务目标 订单服务 发送开始充电指令 到EMQX 到设备
//        // 开始充电指令是一个消息 消息对于设备来说 入参
//        // 结合 接口的入参 该 如何设计
//        // 业务目标 业务是什么 业务如果缺少了什么参数，会导致业务无法执行，这些参数就是入参的核心参数
//        // 首先明确业务目标
//        // 订单服务-->开始充电-->
//        // 给具体的设备(设备的信息)-->
//        // 发送具体的指令(指令信息 开始充电指令)-->
//        // 给具体的用户(用户信息) 具体的订单(订单的唯一标识 订单号)
//
//        //业务目标分析结果 告诉桩 用哪个枪 给 哪个用户的哪个订单 开始充电
//        ChargingDto chargingDto = new ChargingDto();
//        chargingDto.setOrderNo(orderNo);
//        chargingDto.setPileId(pileId);
//        chargingDto.setGunId(gunId);
//        chargingDto.setCommand(MqttConstants.START_CHARGING);
//        //消息是字符串类型
//        // java 对象如何转换为 String
//        //消息体对象类型如何转换为 String 序列化 转换为Json
//        //2.1消息的格式>自定义协议 节省资源 小 减少网络的流量 减少CPU的消耗，减少内存消耗
//        //1000订单号
//        //22 桩id
//        //11 枪id
//        //1 指令 开始充电
//        //100022111  固定长度
//        //10000|22|11|1 分隔符 网络 粘包和拆包
//        //2.1消息的格式>json协议 为了课上好理解 易读
//        //序列化 Spring-Boot MVC 已经帮我们自动做好了 对象转json json转对象
//        //Jackson json库
//        //json库 Gson FastJson ...
//        //https://cloud.tencent.com/developer/article/2242912
//        String json = JsonUtils.toJson(chargingDto);
//        return mqttProducer.send(topic,json);
//    }

    /**
     * 发送开始充电指令给EMQX 通知设备
     * @return
     */
    private Boolean startCharging(String orderNo,
                                  Integer pileId,
                                  Integer gunId){
        //1> Topic的设计
        // 枪维度   一个枪一个Topic topic_桩id_枪id
        // 桩维度   一个桩一个Topic topic_桩id
        // 用桩维度 的topic的数量会比 枪维度的topic数量理论上会少 节省资源
        // 用桩维度 消息体应该包含枪信息  枪id
        // /topic/start/桩id  /topic/start/1
        String topic = MqttConstants.TOPIC_START_CHARGING + pileId;

        // 2> 消息体的设计 包含什么样的数据 通过业务目标分析
        // 业务目标 订单服务 发送开始充电指令 到EMQX 到设备
        // 开始充电指令是一个消息 消息对于设备来说 入参
        // 结合 接口的入参 该 如何设计
        // 业务目标 业务是什么 业务如果缺少了什么参数，会导致业务无法执行，这些参数就是入参的核心参数
        // 首先明确业务目标
        // 订单服务-->开始充电-->
        // 给具体的设备(设备的信息)-->
        // 发送具体的指令(指令信息 开始充电指令)-->
        // 给具体的用户(用户信息) 具体的订单(订单的唯一标识 订单号)

        //业务目标分析结果 告诉桩 用哪个枪 给 哪个用户的哪个订单 开始充电
        ChargingDto chargingDto = new ChargingDto();
        chargingDto.setOrderNo(orderNo);
        chargingDto.setPileId(pileId);
        chargingDto.setGunId(gunId);
        chargingDto.setCommand(MqttConstants.START_CHARGING);
        //todo java中的对象 要调用 MqttProducer#send(String topic,String message)
        //消息是字符串类型
        // java 对象如何转换为 String
        //消息体对象类型如何转换为 String 序列化 转换为Json
        //2.1消息的格式>自定义协议 节省资源 小 减少网络的流量 减少CPU的消耗，减少内存消耗
        //1000订单号
        //22 桩id
        //11 枪id
        //1 指令 开始充电
        //100022111  固定长度
        //10000|22|11|1 分隔符 网络 粘包和拆包
        //2.1消息的格式>json协议 为了课上好理解 易读
        //序列化 Spring-Boot MVC 已经帮我们自动做好了 对象转json json转对象
        //Jackson json库
        //json库 Gson FastJson ...
        //https://cloud.tencent.com/developer/article/2242912
        String json = JsonUtils.toJson(chargingDto);
        return mqttProducer.send(topic,json);
    }

    /**
     * 生成订单号
     * @return
     */
    private String getOrderNo(){
        return "666";
    }

    @Autowired
    private DeviceClient deviceClient;

    /**
     * 通过枪id获取场站信息
     * @param gunId
     * @return
     */
    private StationInfoVO getStationInfo(Integer gunId) {
        System.out.println("xxxxxxxxxxxxxxxx" +   deviceClient);
        return deviceClient.getStationInfo(gunId).getData();
    }

    /**
     * 修改枪状态
     * @param gunId
     * @param status
     * @return
     */
    private Boolean updateGunStatus(Integer gunId,Integer status) {
        GunStatusUpdateParam param = new GunStatusUpdateParam();
        param.setGunId(gunId);
        param.setStatus(status);
        return deviceClient.updateGunStatus(param).getData();
    }


    @Autowired
    private UserClient userClient;

    /**
     * 订单服务
     *  通过 open-feign 服务之间通信的工具
     *  注册中心 服务注册 和 服务发现
     *  LB 负载均衡
     * 调用用户服务
     * @param userId
     * @return
     */
    private UserInfoVO getUserInfoByOpenFeign(Integer userId) {
        return userClient.getUserInfo(userId).getData();
    }

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 通过RestTemplate 调用用户服务的 获取用户详情接口
     * @param userId
     * @return
     */
    private UserInfoVO getUserInfoByUserId(Integer userId) {
        //用户服务的获取用户信息接口地址
        //String url = "http://localhost:7070/user/info/" + userId;
        //在代码中写死这种容易变化的东西 硬编码 写死 不好
        //String url = "http://localhost:7070/user/info/{userId}";
        String randomUrl = getRandomUrl();
        log.debug("随机获取用户服务的地址:{}",randomUrl);
        ParameterizedTypeReference<JsonResult<UserInfoVO>> typeRef =
                new ParameterizedTypeReference<JsonResult<UserInfoVO>>() {
                };

        ResponseEntity<JsonResult<UserInfoVO>> entity =
                restTemplate.exchange(randomUrl,
                        HttpMethod.GET,
                        null, typeRef,userId);
        JsonResult<UserInfoVO> body = entity.getBody();
        return  body.getData();
    }

    /**
     * 随机获取用户服务地址 (负载均衡)
     * 1. 把用户服务的 URL 放到数组
     * 2. 获取 随机数 通过JavaAPI Random
     * 3. 把随机数作为数组的下标,去数组 通过 下标 获取 用户服务URL
     * 4. 通过URL调用用户服务
     * @return
     */
    private String getRandomUrl(){
        String url = "http://localhost:7070/user/info/{userId}";
        String url1 = "http://localhost:7071/user/info/{userId}";
        String url2 = "http://localhost:7072/user/info/{userId}";

        String[] urls = new String[]{url,url1,url2};
        Random random = new Random();
        int index = random.nextInt(urls.length);
        return urls[index];
    }
}
